Mapping

library(tidyverse)
There were 12 warnings (use warnings() to see them)
library(leaflet)
library(lubridate)
library(viridis)

In this tutorial, we are going to learn how to map with leaflet - an Open Source javascript library for building Web mapping applications. We are going to build a map from the dataset we were working with earlier today one component at a time. In this sense, this Notebook can serve as a reference for rendering future maps in R. For more detail and documentation, see this site.

To start, let’s initiate the map by calling leaflet(), setting the View, and adding some Provider Tiles. Run the code below.

leaflet() %>%
  setView(lat = 40.7, lng = -100.0, zoom = 3) %>%
  addProviderTiles("OpenStreetMap")

Provider tiles designate how the base map will appear. Above, we usee OpenStreetMap, a popular open source map, as our base map. However, we can also swap out this base map for other basemaps. See how the map changes when we run the code below.

leaflet() %>%
  setView(lat = 40.7, lng = -100.0, zoom = 3) %>%
  addProviderTiles("CartoDB.Positron")


leaflet() %>%
  setView(lat = 40.7, lng = -100.0, zoom = 3) %>%
  addProviderTiles("Esri.NatGeoWorldMap")


leaflet() %>%
  setView(lat = 40.7, lng = -100.0, zoom = 3) %>%
  addProviderTiles("Stamen.Toner")

NA

What if I wanted to center this on San Francisco? How could I find the right coordinates? What zoom level would be most appropriate?

leaflet() %>%
  setView(lat = 37.7749, lng = -122.4194, zoom = 11) %>%
  addProviderTiles("OpenStreetMap")

Now let’s add our affordable housing data to this map, creating markers for each of the projects. We will do this using addMarkers() and referencing our dataframe. How did leaflet know where to place the markers?

leaflet() %>%
  setView(lat = 37.7749, lng = -122.4194, zoom = 11) %>%
  addProviderTiles("OpenStreetMap") %>%
  addMarkers(data = affordable_housing_sf)
Assuming "longitude" and "latitude" are longitude and latitude, respectively

Let’s add labels for each of these markers so that we know what they refer to when we hover over them.

leaflet() %>%
  setView(lat = 37.7749, lng = -122.4194, zoom = 11) %>%
  addProviderTiles("OpenStreetMap") %>%
  addMarkers(data = affordable_housing_sf,
             label = ~project_name)
Assuming "longitude" and "latitude" are longitude and latitude, respectively

There are so many markers on this map that it can be a bit difficult to make sense of what we are seeing. In leaflet, you have the option to cluster markers. Let’s turn this option on and see what it does to our map.

leaflet() %>%
  setView(lat = 37.7749, lng = -122.4194, zoom = 11) %>%
  addProviderTiles("OpenStreetMap") %>%
  addMarkers(data = affordable_housing_sf,
             label = ~project_name, 
             clusterOptions = markerClusterOptions())
Assuming "longitude" and "latitude" are longitude and latitude, respectively

Markers are limited to showing us the location of points on our map. What if we wanted to visualize more information about each of these points? To start this, we are going to convert the Markers to CircleMarkers.

leaflet() %>%
  setView(lat = 37.7749, lng = -122.4194, zoom = 11) %>%
  addProviderTiles("OpenStreetMap") %>%
  addCircleMarkers(data = affordable_housing_sf,
             label = ~project_name,
             radius = ~2, #sets size of circle
             stroke = FALSE, #removes the outline
             color = '#5a309b', #sets the color
             fillOpacity = 0.8 #sets the opacity
             )
Assuming "longitude" and "latitude" are longitude and latitude, respectively

It can be challenging to make out the markers on the Open Street Map base map because there are so many other colors on the map. Let’s change the base map to one with fewer colors. CartoDB.Positron is a good option for this because it is in greyscale.

leaflet() %>%
  setView(lat = 37.7749, lng = -122.4194, zoom = 11) %>%
  addProviderTiles("CartoDB.Positron") %>%
  addCircleMarkers(data = affordable_housing_sf,
             label = ~project_name,
             radius = ~2, #sets size of circle
             stroke = FALSE, #removes the outline
             color = '#5a309b', #sets the color
             fillOpacity = 0.8 #sets the opacity
             )
Assuming "longitude" and "latitude" are longitude and latitude, respectively

Now begin to visualize more data on the map by coloring the circle markers according to a numeric variable in our dataset. To do this, we are first going to create a color palette, which will indicate a gradient of colors to represent on the map. Here we are going to create a gradient of reds, starting at 1 and ranging to the max of number of affording uints in the dataset. Note that there are a number of different R packages that allow you to create color palettes, including RColorBrewer and viridis.

pal_num <- colorNumeric(palette="Reds", domain = c(1:max(affordable_housing_sf$affordable_units)))

Then we will change the color of the circles in our previous map to a numeric variable (affordable_units) in our datset, wrapped wtih the palette.

leaflet() %>%
  setView(lat = 37.7749, lng = -122.4194, zoom = 11) %>%
  addProviderTiles("CartoDB.Positron") %>%
  addCircleMarkers(data = affordable_housing_sf,
             label = ~project_name,
             radius = 4, 
             stroke = FALSE,
             color = ~pal_num(affordable_units),
             fillOpacity = 0.8 )
Assuming "longitude" and "latitude" are longitude and latitude, respectively
Some values were outside the color scale and will be treated as NASome values were outside the color scale and will be treated as NA

Let’s also add a legend to our map so that we know what these colors refer to. With this information can you tell where are the most affordable units are planned?

leaflet() %>%
  setView(lat = 37.7749, lng = -122.4194, zoom = 11) %>%
  addProviderTiles("CartoDB.Positron") %>%
  addCircleMarkers(data = affordable_housing_sf,
             label = ~project_name,
             radius = 4, 
             stroke = FALSE,
             color = ~pal_num(affordable_units),
             fillOpacity = 0.8 ) %>%
  addLegend(title = "Affordable Units", pal = pal_num, values = c(1:max(affordable_housing_sf$affordable_units)), position = "bottomright")
Assuming "longitude" and "latitude" are longitude and latitude, respectively
Some values were outside the color scale and will be treated as NASome values were outside the color scale and will be treated as NA

Above the color of each circle corresponds to the exact number in affordable_units represented in that observation; this is just one way to divide colors in leaflet. We can also divide the values in our dataset into a designated number of bins. Below we divide the values in our dataset into 7 bins - each with a different color along the gradient. How does this map differ from the previous map?

pal_bin <- colorBin(palette="Reds", domain = c(1:max(affordable_housing_sf$affordable_units)), bins = 7)

leaflet() %>%
  setView(lat = 37.7749, lng = -122.4194, zoom = 11) %>%
  addProviderTiles("CartoDB.Positron") %>%
  addCircleMarkers(data = affordable_housing_sf,
             label = ~project_name,
             radius = 4, 
             stroke = FALSE,
             color = ~pal_bin(affordable_units),
             fillOpacity = 0.8 ) %>%
  addLegend(title = "Affordable Units", pal = pal_bin, values = c(1:max(affordable_housing_sf$affordable_units)), position = "bottomright")
Assuming "longitude" and "latitude" are longitude and latitude, respectively

Finally, we can bin our data by a function called quantiles. In this case, instead of dividing the values into a certain number of equal interval bins, we can divide an equal number of observations into a specificed number of bins. In other words, with colorBin, each bin is equal in interval, while with colorQuantile, each bin is equal in number of observations. What’s the difference betwen the plot above and the plot below? Why/when would we use quantiles instead of bins?

pal_quant <- colorQuantile(palette="Reds", domain = c(1:max(affordable_housing_sf$affordable_units)), n = 6)

leaflet() %>%
  setView(lat = 37.7749, lng = -122.4194, zoom = 11) %>%
  addProviderTiles("CartoDB.Positron") %>%
  addCircleMarkers(data = affordable_housing_sf,
             label = ~project_name,
             radius = 4, 
             stroke = FALSE,
             color = ~pal_quant(affordable_units),
             fillOpacity = 0.8 ) %>%
  addLegend(title = "Affordable Units", pal = pal_quant, values = c(1:max(affordable_housing_sf$affordable_units)), position = "bottomright")
Assuming "longitude" and "latitude" are longitude and latitude, respectively
Some values were outside the color scale and will be treated as NASome values were outside the color scale and will be treated as NA

What if we wanted to visualize a categorical variable on the map instead of a numeric variable. To do this we would need to set up a categorical color palette, using colorFactor. We will use the viridis package to create this palette. To specify a palette with the viridis package, we need to call viridis and then specify the number of colors in the palette (e.g. viridis(7)). Below we specify that the number of colors in the palette should be equal to the number of unique values in the variable we wish to color the plot by. The domain, then, is those unique values. Are there certain areas in the city where there are more projects in the preliminary phase?

pal_cat <- colorFactor(palette = viridis(length(unique(affordable_housing_sf$project_status))), domain = unique(affordable_housing_sf$project_status))

leaflet() %>%
  setView(lat = 37.7749, lng = -122.4194, zoom = 11) %>%
  addProviderTiles("CartoDB.Positron") %>%
  addCircleMarkers(data = affordable_housing_sf,
             label = ~project_name,
             radius = 4, #sets size of circle
             stroke = FALSE,
             color = ~pal_cat(project_status),
             fillOpacity = 0.8 ) %>%
    addLegend(title = "Project Status", pal = pal_cat, values = unique(affordable_housing_sf$project_status), position = "bottomright")
Assuming "longitude" and "latitude" are longitude and latitude, respectively

All of the functions above allowed us to visualize point data, but what if we wanted to visualize affordable housing by neighborhoods, supervisor districts, or zip codes in San Francisco. To do this, we would need a shapefile with polygons representing these geographic boundaries. A popular package for importing and working with shapefiles in R is sf. Below we import a shapefile of planning neighborhoods using the sf package.

library(sf)
sf_planning_neighborhoods <- st_read("https://data.sfgov.org/resource/xfcw-9evu.geojson")
Reading layer `xfcw-9evu' from data source `https://data.sfgov.org/resource/xfcw-9evu.geojson' using driver `GeoJSON'
Simple feature collection with 41 features and 1 field
geometry type:  MULTIPOLYGON
dimension:      XY
bbox:           xmin: -122.5149 ymin: 37.70813 xmax: -122.357 ymax: 37.8333
CRS:            4326

We can place these polygons on our map via addPolygons.

leaflet() %>%
  setView(lat = 37.7749, lng = -122.4194, zoom = 11) %>%
  addProviderTiles("CartoDB.Positron") %>%
  addPolygons(data = sf_planning_neighborhoods)

Just like we did with markers above, let’s adjust the aesthetics of these polygons.

leaflet() %>%
  setView(lat = 37.7749, lng = -122.4194, zoom = 11) %>%
  addProviderTiles("CartoDB.Positron") %>%
  addPolygons(data = sf_planning_neighborhoods, 
              fillColor = '#cccccc', #fill color of the polygon
              fillOpacity = 0.8, #fill opacity of the polygon
              color = "#444444", #color of the outline
              weight = 1, #weight of the outline
              smoothFactor = 0.5 #how much to smooth out the lines in teh polygons
              )

Now let’s join our original dataset to this new dataset so that we can map our data according to this geometry. There are a number of different neighborhood shapefiles on the SF Open Data Portal. This is not uncommon. Different city agencies have different ways of dividing up a city. Further, different community groups may have different ways of dividing up a city, and none of this accounts for real estate/development companies, who can have profit-based incentives to redefine the boundaries of neighborhoods as communities gentrify.

The closest to the neighborhoods in our dataset is called Analysis Neighborhoods, but the names of the neighborhoods across these two datasets still don’t entirely match. Because of this, we will not join by the name of the neighborhood, but instead by their shared geometry (every lat/long that is inside the polygon).

#Convert affordable_housing_sf to an sf object so that we can join it to the neighborhoods shapefile
affordable_housing_sf_sf <- st_as_sf(affordable_housing_sf, coords = c("longitude", "latitude"), crs = 4326)

#Join names and shapes from sf_planning_neighborhoods points to points from affordable_housing_sf when those points are in the shape
affordable_housing_sf_joined <- st_join(affordable_housing_sf_sf, sf_planning_neighborhoods, join = st_within)
although coordinates are longitude/latitude, st_within assumes that they are planar
#Calculate the number of affordable units in each neighborhood
affordable_units_in_neighborhood <- 
  as_tibble(affordable_housing_sf_joined) %>% 
  group_by(nhood) %>% 
  summarize(affordable_units = sum(affordable_units), .groups = 'drop') %>%
  ungroup()

#Join the calculated affordable units per neighborhood back to the sf_planning_neighborhoods shapefile
affordable_units_in_neighborhood_sf <- left_join(sf_planning_neighborhoods, affordable_units_in_neighborhood)
Joining, by = "nhood"

Now using similar strategies as above we will color the map by the number of affordable units planned for the neighborhood.

pal_bin <- colorBin(palette="Reds", domain = affordable_units_in_neighborhood_sf$affordable_units, bins = 10)

leaflet() %>%
  setView(lat = 37.7749, lng = -122.4194, zoom = 11) %>%
  addProviderTiles("CartoDB.Positron") %>%
  addPolygons(data = affordable_units_in_neighborhood_sf, 
              label = ~nhood,
              fillColor = ~pal_bin(affordable_units), #fill color of the polygon
              fillOpacity = 0.8, #fill opacity of the polygon
              color = "#444444", #color of the outline
              weight = 1, #weight of the outline
              smoothFactor = 0.5 #how much to smooth out the lines in teh polygons
              ) %>%
  addLegend(title = "Affordable Units", pal = pal_bin, values = affordable_units_in_neighborhood_sf$affordable_units, position = "bottomright") %>%
  addCircleMarkers(data = affordable_housing_sf,
             label = ~project_name,
             radius = ~2, #sets size of circle
             stroke = FALSE, #removes the outline
             color = '#5a309b', #sets the color
             fillOpacity = 0.8 #sets the opacity
             )
n too large, allowed maximum for palette Reds is 9
Returning the palette you asked for with that many colors
n too large, allowed maximum for palette Reds is 9
Returning the palette you asked for with that many colors
Assuming "longitude" and "latitude" are longitude and latitude, respectively
LS0tCnRpdGxlOiAiTWFwcGluZyIKb3V0cHV0OiBodG1sX25vdGVib29rCmVkaXRvcl9vcHRpb25zOiAKICBjaHVua19vdXRwdXRfdHlwZTogaW5saW5lCi0tLQoKIyBNYXBwaW5nCgpgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkobGVhZmxldCkKbGlicmFyeShsdWJyaWRhdGUpCmxpYnJhcnkodmlyaWRpcykKYGBgCgpJbiB0aGlzIHR1dG9yaWFsLCB3ZSBhcmUgZ29pbmcgdG8gbGVhcm4gaG93IHRvIG1hcCB3aXRoIGxlYWZsZXQgLSBhbiBPcGVuIFNvdXJjZSBqYXZhc2NyaXB0IGxpYnJhcnkgZm9yIGJ1aWxkaW5nIFdlYiBtYXBwaW5nIGFwcGxpY2F0aW9ucy4gV2UgYXJlIGdvaW5nIHRvIGJ1aWxkIGEgbWFwIGZyb20gdGhlIGRhdGFzZXQgd2Ugd2VyZSB3b3JraW5nIHdpdGggZWFybGllciB0b2RheSBvbmUgY29tcG9uZW50IGF0IGEgdGltZS4gSW4gdGhpcyBzZW5zZSwgdGhpcyBOb3RlYm9vayBjYW4gc2VydmUgYXMgYSByZWZlcmVuY2UgZm9yIHJlbmRlcmluZyBmdXR1cmUgbWFwcyBpbiBSLiBGb3IgbW9yZSBkZXRhaWwgYW5kIGRvY3VtZW50YXRpb24sIHNlZSBbdGhpcyBzaXRlXShodHRwczovL3JzdHVkaW8uZ2l0aHViLmlvL2xlYWZsZXQvKS4gCgpUbyBzdGFydCwgbGV0J3MgaW5pdGlhdGUgdGhlIG1hcCBieSBjYWxsaW5nIGxlYWZsZXQoKSwgc2V0dGluZyB0aGUgVmlldywgYW5kIGFkZGluZyBzb21lIFByb3ZpZGVyIFRpbGVzLiBSdW4gdGhlIGNvZGUgYmVsb3cuCgpgYGB7cn0KbGVhZmxldCgpICU+JQogIHNldFZpZXcobGF0ID0gNDAuNywgbG5nID0gLTEwMC4wLCB6b29tID0gMykgJT4lCiAgYWRkUHJvdmlkZXJUaWxlcygiT3BlblN0cmVldE1hcCIpCmBgYAoKUHJvdmlkZXIgdGlsZXMgZGVzaWduYXRlIGhvdyB0aGUgYmFzZSBtYXAgd2lsbCBhcHBlYXIuIEFib3ZlLCB3ZSB1c2VlIE9wZW5TdHJlZXRNYXAsIGEgcG9wdWxhciBvcGVuIHNvdXJjZSBtYXAsIGFzIG91ciBiYXNlIG1hcC4gSG93ZXZlciwgd2UgY2FuIGFsc28gc3dhcCBvdXQgdGhpcyBiYXNlIG1hcCBmb3Igb3RoZXIgYmFzZW1hcHMuIFNlZSBob3cgdGhlIG1hcCBjaGFuZ2VzIHdoZW4gd2UgcnVuIHRoZSBjb2RlIGJlbG93LiAKCmBgYHtyfQpsZWFmbGV0KCkgJT4lCiAgc2V0VmlldyhsYXQgPSA0MC43LCBsbmcgPSAtMTAwLjAsIHpvb20gPSAzKSAlPiUKICBhZGRQcm92aWRlclRpbGVzKCJDYXJ0b0RCLlBvc2l0cm9uIikKCmxlYWZsZXQoKSAlPiUKICBzZXRWaWV3KGxhdCA9IDQwLjcsIGxuZyA9IC0xMDAuMCwgem9vbSA9IDMpICU+JQogIGFkZFByb3ZpZGVyVGlsZXMoIkVzcmkuTmF0R2VvV29ybGRNYXAiKQoKbGVhZmxldCgpICU+JQogIHNldFZpZXcobGF0ID0gNDAuNywgbG5nID0gLTEwMC4wLCB6b29tID0gMykgJT4lCiAgYWRkUHJvdmlkZXJUaWxlcygiU3RhbWVuLlRvbmVyIikKCmBgYAoKCldoYXQgaWYgSSB3YW50ZWQgdG8gY2VudGVyIHRoaXMgb24gU2FuIEZyYW5jaXNjbz8gSG93IGNvdWxkIEkgZmluZCB0aGUgcmlnaHQgY29vcmRpbmF0ZXM/IFdoYXQgem9vbSBsZXZlbCB3b3VsZCBiZSBtb3N0IGFwcHJvcHJpYXRlPwoKYGBge3J9CmxlYWZsZXQoKSAlPiUKICBzZXRWaWV3KGxhdCA9IDM3Ljc3NDksIGxuZyA9IC0xMjIuNDE5NCwgem9vbSA9IDExKSAlPiUKICBhZGRQcm92aWRlclRpbGVzKCJPcGVuU3RyZWV0TWFwIikKYGBgCgpOb3cgbGV0J3MgYWRkIG91ciBhZmZvcmRhYmxlIGhvdXNpbmcgZGF0YSB0byB0aGlzIG1hcCwgY3JlYXRpbmcgbWFya2VycyBmb3IgZWFjaCBvZiB0aGUgcHJvamVjdHMuIFdlIHdpbGwgZG8gdGhpcyB1c2luZyBhZGRNYXJrZXJzKCkgYW5kIHJlZmVyZW5jaW5nIG91ciBkYXRhZnJhbWUuIEhvdyBkaWQgbGVhZmxldCBrbm93IHdoZXJlIHRvIHBsYWNlIHRoZSBtYXJrZXJzPwoKYGBge3J9CmxlYWZsZXQoKSAlPiUKICBzZXRWaWV3KGxhdCA9IDM3Ljc3NDksIGxuZyA9IC0xMjIuNDE5NCwgem9vbSA9IDExKSAlPiUKICBhZGRQcm92aWRlclRpbGVzKCJPcGVuU3RyZWV0TWFwIikgJT4lCiAgYWRkTWFya2VycyhkYXRhID0gYWZmb3JkYWJsZV9ob3VzaW5nX3NmKQpgYGAKCkxldCdzIGFkZCBsYWJlbHMgZm9yIGVhY2ggb2YgdGhlc2UgbWFya2VycyBzbyB0aGF0IHdlIGtub3cgd2hhdCB0aGV5IHJlZmVyIHRvIHdoZW4gd2UgaG92ZXIgb3ZlciB0aGVtLiAKCmBgYHtyfQpsZWFmbGV0KCkgJT4lCiAgc2V0VmlldyhsYXQgPSAzNy43NzQ5LCBsbmcgPSAtMTIyLjQxOTQsIHpvb20gPSAxMSkgJT4lCiAgYWRkUHJvdmlkZXJUaWxlcygiT3BlblN0cmVldE1hcCIpICU+JQogIGFkZE1hcmtlcnMoZGF0YSA9IGFmZm9yZGFibGVfaG91c2luZ19zZiwKICAgICAgICAgICAgIGxhYmVsID0gfnByb2plY3RfbmFtZSkKYGBgCgpUaGVyZSBhcmUgc28gbWFueSBtYXJrZXJzIG9uIHRoaXMgbWFwIHRoYXQgaXQgY2FuIGJlIGEgYml0IGRpZmZpY3VsdCB0byBtYWtlIHNlbnNlIG9mIHdoYXQgd2UgYXJlIHNlZWluZy4gSW4gbGVhZmxldCwgeW91IGhhdmUgdGhlIG9wdGlvbiB0byBjbHVzdGVyIG1hcmtlcnMuIExldCdzIHR1cm4gdGhpcyBvcHRpb24gb24gYW5kIHNlZSB3aGF0IGl0IGRvZXMgdG8gb3VyIG1hcC4KCmBgYHtyfQpsZWFmbGV0KCkgJT4lCiAgc2V0VmlldyhsYXQgPSAzNy43NzQ5LCBsbmcgPSAtMTIyLjQxOTQsIHpvb20gPSAxMSkgJT4lCiAgYWRkUHJvdmlkZXJUaWxlcygiT3BlblN0cmVldE1hcCIpICU+JQogIGFkZE1hcmtlcnMoZGF0YSA9IGFmZm9yZGFibGVfaG91c2luZ19zZiwKICAgICAgICAgICAgIGxhYmVsID0gfnByb2plY3RfbmFtZSwgCiAgICAgICAgICAgICBjbHVzdGVyT3B0aW9ucyA9IG1hcmtlckNsdXN0ZXJPcHRpb25zKCkpCmBgYAoKTWFya2VycyBhcmUgbGltaXRlZCB0byBzaG93aW5nIHVzIHRoZSBsb2NhdGlvbiBvZiBwb2ludHMgb24gb3VyIG1hcC4gV2hhdCBpZiB3ZSB3YW50ZWQgdG8gdmlzdWFsaXplIG1vcmUgaW5mb3JtYXRpb24gYWJvdXQgZWFjaCBvZiB0aGVzZSBwb2ludHM/IFRvIHN0YXJ0IHRoaXMsIHdlIGFyZSBnb2luZyB0byBjb252ZXJ0IHRoZSBNYXJrZXJzIHRvIENpcmNsZU1hcmtlcnMuCgpgYGB7cn0KbGVhZmxldCgpICU+JQogIHNldFZpZXcobGF0ID0gMzcuNzc0OSwgbG5nID0gLTEyMi40MTk0LCB6b29tID0gMTEpICU+JQogIGFkZFByb3ZpZGVyVGlsZXMoIk9wZW5TdHJlZXRNYXAiKSAlPiUKICBhZGRDaXJjbGVNYXJrZXJzKGRhdGEgPSBhZmZvcmRhYmxlX2hvdXNpbmdfc2YsCiAgICAgICAgICAgICBsYWJlbCA9IH5wcm9qZWN0X25hbWUsCiAgICAgICAgICAgICByYWRpdXMgPSB+MiwgI3NldHMgc2l6ZSBvZiBjaXJjbGUKICAgICAgICAgICAgIHN0cm9rZSA9IEZBTFNFLCAjcmVtb3ZlcyB0aGUgb3V0bGluZQogICAgICAgICAgICAgY29sb3IgPSAnIzVhMzA5YicsICNzZXRzIHRoZSBjb2xvcgogICAgICAgICAgICAgZmlsbE9wYWNpdHkgPSAwLjggI3NldHMgdGhlIG9wYWNpdHkKICAgICAgICAgICAgICkKYGBgCgpJdCBjYW4gYmUgY2hhbGxlbmdpbmcgdG8gbWFrZSBvdXQgdGhlIG1hcmtlcnMgb24gdGhlIE9wZW4gU3RyZWV0IE1hcCBiYXNlIG1hcCBiZWNhdXNlIHRoZXJlIGFyZSBzbyBtYW55IG90aGVyIGNvbG9ycyBvbiB0aGUgbWFwLiBMZXQncyBjaGFuZ2UgdGhlIGJhc2UgbWFwIHRvIG9uZSB3aXRoIGZld2VyIGNvbG9ycy4gQ2FydG9EQi5Qb3NpdHJvbiBpcyBhIGdvb2Qgb3B0aW9uIGZvciB0aGlzIGJlY2F1c2UgaXQgaXMgaW4gZ3JleXNjYWxlLiAKCmBgYHtyfQpsZWFmbGV0KCkgJT4lCiAgc2V0VmlldyhsYXQgPSAzNy43NzQ5LCBsbmcgPSAtMTIyLjQxOTQsIHpvb20gPSAxMSkgJT4lCiAgYWRkUHJvdmlkZXJUaWxlcygiQ2FydG9EQi5Qb3NpdHJvbiIpICU+JQogIGFkZENpcmNsZU1hcmtlcnMoZGF0YSA9IGFmZm9yZGFibGVfaG91c2luZ19zZiwKICAgICAgICAgICAgIGxhYmVsID0gfnByb2plY3RfbmFtZSwKICAgICAgICAgICAgIHJhZGl1cyA9IH4yLCAjc2V0cyBzaXplIG9mIGNpcmNsZQogICAgICAgICAgICAgc3Ryb2tlID0gRkFMU0UsICNyZW1vdmVzIHRoZSBvdXRsaW5lCiAgICAgICAgICAgICBjb2xvciA9ICcjNWEzMDliJywgI3NldHMgdGhlIGNvbG9yCiAgICAgICAgICAgICBmaWxsT3BhY2l0eSA9IDAuOCAjc2V0cyB0aGUgb3BhY2l0eQogICAgICAgICAgICAgKQpgYGAKCgpOb3cgYmVnaW4gdG8gdmlzdWFsaXplIG1vcmUgZGF0YSBvbiB0aGUgbWFwIGJ5IGNvbG9yaW5nIHRoZSBjaXJjbGUgbWFya2VycyBhY2NvcmRpbmcgdG8gYSBudW1lcmljIHZhcmlhYmxlIGluIG91ciBkYXRhc2V0LiBUbyBkbyB0aGlzLCB3ZSBhcmUgZmlyc3QgZ29pbmcgdG8gY3JlYXRlIGEgY29sb3IgcGFsZXR0ZSwgd2hpY2ggd2lsbCBpbmRpY2F0ZSBhIGdyYWRpZW50IG9mIGNvbG9ycyB0byByZXByZXNlbnQgb24gdGhlIG1hcC4gSGVyZSB3ZSBhcmUgZ29pbmcgdG8gY3JlYXRlIGEgZ3JhZGllbnQgb2YgcmVkcywgc3RhcnRpbmcgYXQgMSBhbmQgcmFuZ2luZyB0byB0aGUgbWF4IG9mIG51bWJlciBvZiBhZmZvcmRpbmcgdWludHMgaW4gdGhlIGRhdGFzZXQuIE5vdGUgdGhhdCB0aGVyZSBhcmUgYSBudW1iZXIgb2YgZGlmZmVyZW50IFIgcGFja2FnZXMgdGhhdCBhbGxvdyB5b3UgdG8gY3JlYXRlIGNvbG9yIHBhbGV0dGVzLCBpbmNsdWRpbmcgW1JDb2xvckJyZXdlcl0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL1JDb2xvckJyZXdlci9SQ29sb3JCcmV3ZXIucGRmKSBhbmQgW3ZpcmlkaXNdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy92aXJpZGlzL2luZGV4Lmh0bWwpLgoKYGBge3J9CnBhbF9udW0gPC0gY29sb3JOdW1lcmljKHBhbGV0dGU9IlJlZHMiLCBkb21haW4gPSBjKDE6bWF4KGFmZm9yZGFibGVfaG91c2luZ19zZiRhZmZvcmRhYmxlX3VuaXRzKSkpCmBgYAoKVGhlbiB3ZSB3aWxsIGNoYW5nZSB0aGUgY29sb3Igb2YgdGhlIGNpcmNsZXMgaW4gb3VyIHByZXZpb3VzIG1hcCB0byBhIG51bWVyaWMgdmFyaWFibGUgKGFmZm9yZGFibGVfdW5pdHMpIGluIG91ciBkYXRzZXQsIHdyYXBwZWQgd3RpaCB0aGUgcGFsZXR0ZS4gCgpgYGB7cn0KbGVhZmxldCgpICU+JQogIHNldFZpZXcobGF0ID0gMzcuNzc0OSwgbG5nID0gLTEyMi40MTk0LCB6b29tID0gMTEpICU+JQogIGFkZFByb3ZpZGVyVGlsZXMoIkNhcnRvREIuUG9zaXRyb24iKSAlPiUKICBhZGRDaXJjbGVNYXJrZXJzKGRhdGEgPSBhZmZvcmRhYmxlX2hvdXNpbmdfc2YsCiAgICAgICAgICAgICBsYWJlbCA9IH5wcm9qZWN0X25hbWUsCiAgICAgICAgICAgICByYWRpdXMgPSA0LCAKICAgICAgICAgICAgIHN0cm9rZSA9IEZBTFNFLAogICAgICAgICAgICAgY29sb3IgPSB+cGFsX251bShhZmZvcmRhYmxlX3VuaXRzKSwKICAgICAgICAgICAgIGZpbGxPcGFjaXR5ID0gMC44ICkKYGBgCgpMZXQncyBhbHNvIGFkZCBhIGxlZ2VuZCB0byBvdXIgbWFwIHNvIHRoYXQgd2Uga25vdyB3aGF0IHRoZXNlIGNvbG9ycyByZWZlciB0by4gV2l0aCB0aGlzIGluZm9ybWF0aW9uIGNhbiB5b3UgdGVsbCB3aGVyZSBhcmUgdGhlIG1vc3QgYWZmb3JkYWJsZSB1bml0cyBhcmUgcGxhbm5lZD8KCmBgYHtyfQpsZWFmbGV0KCkgJT4lCiAgc2V0VmlldyhsYXQgPSAzNy43NzQ5LCBsbmcgPSAtMTIyLjQxOTQsIHpvb20gPSAxMSkgJT4lCiAgYWRkUHJvdmlkZXJUaWxlcygiQ2FydG9EQi5Qb3NpdHJvbiIpICU+JQogIGFkZENpcmNsZU1hcmtlcnMoZGF0YSA9IGFmZm9yZGFibGVfaG91c2luZ19zZiwKICAgICAgICAgICAgIGxhYmVsID0gfnByb2plY3RfbmFtZSwKICAgICAgICAgICAgIHJhZGl1cyA9IDQsIAogICAgICAgICAgICAgc3Ryb2tlID0gRkFMU0UsCiAgICAgICAgICAgICBjb2xvciA9IH5wYWxfbnVtKGFmZm9yZGFibGVfdW5pdHMpLAogICAgICAgICAgICAgZmlsbE9wYWNpdHkgPSAwLjggKSAlPiUKICBhZGRMZWdlbmQodGl0bGUgPSAiQWZmb3JkYWJsZSBVbml0cyIsIHBhbCA9IHBhbF9udW0sIHZhbHVlcyA9IGMoMTptYXgoYWZmb3JkYWJsZV9ob3VzaW5nX3NmJGFmZm9yZGFibGVfdW5pdHMpKSwgcG9zaXRpb24gPSAiYm90dG9tcmlnaHQiKQpgYGAKCkFib3ZlIHRoZSBjb2xvciBvZiBlYWNoIGNpcmNsZSBjb3JyZXNwb25kcyB0byB0aGUgZXhhY3QgbnVtYmVyIGluIGFmZm9yZGFibGVfdW5pdHMgcmVwcmVzZW50ZWQgaW4gdGhhdCBvYnNlcnZhdGlvbjsgdGhpcyBpcyBqdXN0IG9uZSB3YXkgdG8gZGl2aWRlIGNvbG9ycyBpbiBsZWFmbGV0LiBXZSBjYW4gYWxzbyBkaXZpZGUgdGhlIHZhbHVlcyBpbiBvdXIgZGF0YXNldCBpbnRvIGEgZGVzaWduYXRlZCBudW1iZXIgb2YgYmlucy4gQmVsb3cgd2UgZGl2aWRlIHRoZSB2YWx1ZXMgaW4gb3VyIGRhdGFzZXQgaW50byA3IGJpbnMgLSBlYWNoIHdpdGggYSBkaWZmZXJlbnQgY29sb3IgYWxvbmcgdGhlIGdyYWRpZW50LiBIb3cgZG9lcyB0aGlzIG1hcCBkaWZmZXIgZnJvbSB0aGUgcHJldmlvdXMgbWFwPwoKYGBge3J9CnBhbF9iaW4gPC0gY29sb3JCaW4ocGFsZXR0ZT0iUmVkcyIsIGRvbWFpbiA9IGMoMTptYXgoYWZmb3JkYWJsZV9ob3VzaW5nX3NmJGFmZm9yZGFibGVfdW5pdHMpKSwgYmlucyA9IDcpCgpsZWFmbGV0KCkgJT4lCiAgc2V0VmlldyhsYXQgPSAzNy43NzQ5LCBsbmcgPSAtMTIyLjQxOTQsIHpvb20gPSAxMSkgJT4lCiAgYWRkUHJvdmlkZXJUaWxlcygiQ2FydG9EQi5Qb3NpdHJvbiIpICU+JQogIGFkZENpcmNsZU1hcmtlcnMoZGF0YSA9IGFmZm9yZGFibGVfaG91c2luZ19zZiwKICAgICAgICAgICAgIGxhYmVsID0gfnByb2plY3RfbmFtZSwKICAgICAgICAgICAgIHJhZGl1cyA9IDQsIAogICAgICAgICAgICAgc3Ryb2tlID0gRkFMU0UsCiAgICAgICAgICAgICBjb2xvciA9IH5wYWxfYmluKGFmZm9yZGFibGVfdW5pdHMpLAogICAgICAgICAgICAgZmlsbE9wYWNpdHkgPSAwLjggKSAlPiUKICBhZGRMZWdlbmQodGl0bGUgPSAiQWZmb3JkYWJsZSBVbml0cyIsIHBhbCA9IHBhbF9iaW4sIHZhbHVlcyA9IGMoMTptYXgoYWZmb3JkYWJsZV9ob3VzaW5nX3NmJGFmZm9yZGFibGVfdW5pdHMpKSwgcG9zaXRpb24gPSAiYm90dG9tcmlnaHQiKQoKYGBgCgpGaW5hbGx5LCB3ZSBjYW4gYmluIG91ciBkYXRhIGJ5IGEgZnVuY3Rpb24gY2FsbGVkIHF1YW50aWxlcy4gSW4gdGhpcyBjYXNlLCBpbnN0ZWFkIG9mIGRpdmlkaW5nIHRoZSB2YWx1ZXMgaW50byBhIGNlcnRhaW4gbnVtYmVyIG9mICplcXVhbCBpbnRlcnZhbCBiaW5zKiwgd2UgY2FuIGRpdmlkZSBhbiAqZXF1YWwgbnVtYmVyIG9mIG9ic2VydmF0aW9ucyogaW50byBhIHNwZWNpZmljZWQgbnVtYmVyIG9mIGJpbnMuIEluIG90aGVyIHdvcmRzLCB3aXRoIGNvbG9yQmluLCBlYWNoIGJpbiBpcyBlcXVhbCBpbiBpbnRlcnZhbCwgd2hpbGUgd2l0aCBjb2xvclF1YW50aWxlLCBlYWNoIGJpbiBpcyBlcXVhbCBpbiBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zLiBXaGF0J3MgdGhlIGRpZmZlcmVuY2UgYmV0d2VuIHRoZSBwbG90IGFib3ZlIGFuZCB0aGUgcGxvdCBiZWxvdz8gV2h5L3doZW4gd291bGQgd2UgdXNlIHF1YW50aWxlcyBpbnN0ZWFkIG9mIGJpbnM/CgpgYGB7cn0KcGFsX3F1YW50IDwtIGNvbG9yUXVhbnRpbGUocGFsZXR0ZT0iUmVkcyIsIGRvbWFpbiA9IGMoMTptYXgoYWZmb3JkYWJsZV9ob3VzaW5nX3NmJGFmZm9yZGFibGVfdW5pdHMpKSwgbiA9IDYpCgpsZWFmbGV0KCkgJT4lCiAgc2V0VmlldyhsYXQgPSAzNy43NzQ5LCBsbmcgPSAtMTIyLjQxOTQsIHpvb20gPSAxMSkgJT4lCiAgYWRkUHJvdmlkZXJUaWxlcygiQ2FydG9EQi5Qb3NpdHJvbiIpICU+JQogIGFkZENpcmNsZU1hcmtlcnMoZGF0YSA9IGFmZm9yZGFibGVfaG91c2luZ19zZiwKICAgICAgICAgICAgIGxhYmVsID0gfnByb2plY3RfbmFtZSwKICAgICAgICAgICAgIHJhZGl1cyA9IDQsIAogICAgICAgICAgICAgc3Ryb2tlID0gRkFMU0UsCiAgICAgICAgICAgICBjb2xvciA9IH5wYWxfcXVhbnQoYWZmb3JkYWJsZV91bml0cyksCiAgICAgICAgICAgICBmaWxsT3BhY2l0eSA9IDAuOCApICU+JQogIGFkZExlZ2VuZCh0aXRsZSA9ICJBZmZvcmRhYmxlIFVuaXRzIiwgcGFsID0gcGFsX3F1YW50LCB2YWx1ZXMgPSBjKDE6bWF4KGFmZm9yZGFibGVfaG91c2luZ19zZiRhZmZvcmRhYmxlX3VuaXRzKSksIHBvc2l0aW9uID0gImJvdHRvbXJpZ2h0IikKYGBgCgpXaGF0IGlmIHdlIHdhbnRlZCB0byB2aXN1YWxpemUgYSBjYXRlZ29yaWNhbCB2YXJpYWJsZSBvbiB0aGUgbWFwIGluc3RlYWQgb2YgYSBudW1lcmljIHZhcmlhYmxlLiBUbyBkbyB0aGlzIHdlIHdvdWxkIG5lZWQgdG8gc2V0IHVwIGEgY2F0ZWdvcmljYWwgY29sb3IgcGFsZXR0ZSwgdXNpbmcgY29sb3JGYWN0b3IuIFdlIHdpbGwgdXNlIHRoZSB2aXJpZGlzIHBhY2thZ2UgdG8gY3JlYXRlIHRoaXMgcGFsZXR0ZS4gVG8gc3BlY2lmeSBhIHBhbGV0dGUgd2l0aCB0aGUgdmlyaWRpcyBwYWNrYWdlLCB3ZSBuZWVkIHRvIGNhbGwgdmlyaWRpcyBhbmQgdGhlbiBzcGVjaWZ5IHRoZSBudW1iZXIgb2YgY29sb3JzIGluIHRoZSBwYWxldHRlIChlLmcuIHZpcmlkaXMoNykpLiBCZWxvdyB3ZSBzcGVjaWZ5IHRoYXQgdGhlIG51bWJlciBvZiBjb2xvcnMgaW4gdGhlIHBhbGV0dGUgc2hvdWxkIGJlIGVxdWFsIHRvIHRoZSBudW1iZXIgb2YgdW5pcXVlIHZhbHVlcyBpbiB0aGUgdmFyaWFibGUgd2Ugd2lzaCB0byBjb2xvciB0aGUgcGxvdCBieS4gVGhlIGRvbWFpbiwgdGhlbiwgaXMgdGhvc2UgdW5pcXVlIHZhbHVlcy4gQXJlIHRoZXJlIGNlcnRhaW4gYXJlYXMgaW4gdGhlIGNpdHkgd2hlcmUgdGhlcmUgYXJlIG1vcmUgcHJvamVjdHMgaW4gdGhlIHByZWxpbWluYXJ5IHBoYXNlPwoKYGBge3J9CnBhbF9jYXQgPC0gY29sb3JGYWN0b3IocGFsZXR0ZSA9IHZpcmlkaXMobGVuZ3RoKHVuaXF1ZShhZmZvcmRhYmxlX2hvdXNpbmdfc2YkcHJvamVjdF9zdGF0dXMpKSksIGRvbWFpbiA9IHVuaXF1ZShhZmZvcmRhYmxlX2hvdXNpbmdfc2YkcHJvamVjdF9zdGF0dXMpKQoKbGVhZmxldCgpICU+JQogIHNldFZpZXcobGF0ID0gMzcuNzc0OSwgbG5nID0gLTEyMi40MTk0LCB6b29tID0gMTEpICU+JQogIGFkZFByb3ZpZGVyVGlsZXMoIkNhcnRvREIuUG9zaXRyb24iKSAlPiUKICBhZGRDaXJjbGVNYXJrZXJzKGRhdGEgPSBhZmZvcmRhYmxlX2hvdXNpbmdfc2YsCiAgICAgICAgICAgICBsYWJlbCA9IH5wcm9qZWN0X25hbWUsCiAgICAgICAgICAgICByYWRpdXMgPSA0LCAjc2V0cyBzaXplIG9mIGNpcmNsZQogICAgICAgICAgICAgc3Ryb2tlID0gRkFMU0UsCiAgICAgICAgICAgICBjb2xvciA9IH5wYWxfY2F0KHByb2plY3Rfc3RhdHVzKSwKICAgICAgICAgICAgIGZpbGxPcGFjaXR5ID0gMC44ICkgJT4lCiAgICBhZGRMZWdlbmQodGl0bGUgPSAiUHJvamVjdCBTdGF0dXMiLCBwYWwgPSBwYWxfY2F0LCB2YWx1ZXMgPSB1bmlxdWUoYWZmb3JkYWJsZV9ob3VzaW5nX3NmJHByb2plY3Rfc3RhdHVzKSwgcG9zaXRpb24gPSAiYm90dG9tcmlnaHQiKQoKYGBgCgpBbGwgb2YgdGhlIGZ1bmN0aW9ucyBhYm92ZSBhbGxvd2VkIHVzIHRvIHZpc3VhbGl6ZSAqcG9pbnQqIGRhdGEsIGJ1dCB3aGF0IGlmIHdlIHdhbnRlZCB0byB2aXN1YWxpemUgYWZmb3JkYWJsZSBob3VzaW5nIGJ5IG5laWdoYm9yaG9vZHMsIHN1cGVydmlzb3IgZGlzdHJpY3RzLCBvciB6aXAgY29kZXMgaW4gU2FuIEZyYW5jaXNjby4gVG8gZG8gdGhpcywgd2Ugd291bGQgbmVlZCBhIHNoYXBlZmlsZSB3aXRoIHBvbHlnb25zIHJlcHJlc2VudGluZyB0aGVzZSBnZW9ncmFwaGljIGJvdW5kYXJpZXMuIEEgcG9wdWxhciBwYWNrYWdlIGZvciBpbXBvcnRpbmcgYW5kIHdvcmtpbmcgd2l0aCBzaGFwZWZpbGVzIGluIFIgaXMgc2YuIEJlbG93IHdlIGltcG9ydCBhIHNoYXBlZmlsZSBvZiBwbGFubmluZyBuZWlnaGJvcmhvb2RzIHVzaW5nIHRoZSBzZiBwYWNrYWdlLgoKYGBge3J9CmxpYnJhcnkoc2YpCnNmX3BsYW5uaW5nX25laWdoYm9yaG9vZHMgPC0gc3RfcmVhZCgiaHR0cHM6Ly9kYXRhLnNmZ292Lm9yZy9yZXNvdXJjZS94ZmN3LTlldnUuZ2VvanNvbiIpCmBgYApXZSBjYW4gcGxhY2UgdGhlc2UgcG9seWdvbnMgb24gb3VyIG1hcCB2aWEgYWRkUG9seWdvbnMuIAoKYGBge3J9CmxlYWZsZXQoKSAlPiUKICBzZXRWaWV3KGxhdCA9IDM3Ljc3NDksIGxuZyA9IC0xMjIuNDE5NCwgem9vbSA9IDExKSAlPiUKICBhZGRQcm92aWRlclRpbGVzKCJDYXJ0b0RCLlBvc2l0cm9uIikgJT4lCiAgYWRkUG9seWdvbnMoZGF0YSA9IHNmX3BsYW5uaW5nX25laWdoYm9yaG9vZHMpCmBgYAoKSnVzdCBsaWtlIHdlIGRpZCB3aXRoIG1hcmtlcnMgYWJvdmUsIGxldCdzIGFkanVzdCB0aGUgYWVzdGhldGljcyBvZiB0aGVzZSBwb2x5Z29ucy4KCmBgYHtyfQpsZWFmbGV0KCkgJT4lCiAgc2V0VmlldyhsYXQgPSAzNy43NzQ5LCBsbmcgPSAtMTIyLjQxOTQsIHpvb20gPSAxMSkgJT4lCiAgYWRkUHJvdmlkZXJUaWxlcygiQ2FydG9EQi5Qb3NpdHJvbiIpICU+JQogIGFkZFBvbHlnb25zKGRhdGEgPSBzZl9wbGFubmluZ19uZWlnaGJvcmhvb2RzLCAKICAgICAgICAgICAgICBmaWxsQ29sb3IgPSAnI2NjY2NjYycsICNmaWxsIGNvbG9yIG9mIHRoZSBwb2x5Z29uCiAgICAgICAgICAgICAgZmlsbE9wYWNpdHkgPSAwLjgsICNmaWxsIG9wYWNpdHkgb2YgdGhlIHBvbHlnb24KICAgICAgICAgICAgICBjb2xvciA9ICIjNDQ0NDQ0IiwgI2NvbG9yIG9mIHRoZSBvdXRsaW5lCiAgICAgICAgICAgICAgd2VpZ2h0ID0gMSwgI3dlaWdodCBvZiB0aGUgb3V0bGluZQogICAgICAgICAgICAgIHNtb290aEZhY3RvciA9IDAuNSAjaG93IG11Y2ggdG8gc21vb3RoIG91dCB0aGUgbGluZXMgaW4gdGVoIHBvbHlnb25zCiAgICAgICAgICAgICAgKQpgYGAKCk5vdyBsZXQncyBqb2luIG91ciBvcmlnaW5hbCBkYXRhc2V0IHRvIHRoaXMgbmV3IGRhdGFzZXQgc28gdGhhdCB3ZSBjYW4gbWFwIG91ciBkYXRhIGFjY29yZGluZyB0byB0aGlzIGdlb21ldHJ5LiAKVGhlcmUgYXJlIGEgbnVtYmVyIG9mIGRpZmZlcmVudCBuZWlnaGJvcmhvb2Qgc2hhcGVmaWxlcyBvbiB0aGUgU0YgT3BlbiBEYXRhIFBvcnRhbC4gVGhpcyBpcyBub3QgdW5jb21tb24uIERpZmZlcmVudCBjaXR5IGFnZW5jaWVzIGhhdmUgZGlmZmVyZW50IHdheXMgb2YgZGl2aWRpbmcgdXAgYSBjaXR5LiBGdXJ0aGVyLCBkaWZmZXJlbnQgY29tbXVuaXR5IGdyb3VwcyBtYXkgaGF2ZSBkaWZmZXJlbnQgd2F5cyBvZiBkaXZpZGluZyB1cCBhIGNpdHksIGFuZCBub25lIG9mIHRoaXMgYWNjb3VudHMgZm9yIHJlYWwgZXN0YXRlL2RldmVsb3BtZW50IGNvbXBhbmllcywgd2hvIGNhbiBoYXZlIHByb2ZpdC1iYXNlZCBpbmNlbnRpdmVzIHRvIHJlZGVmaW5lIHRoZSBib3VuZGFyaWVzIG9mIG5laWdoYm9yaG9vZHMgYXMgY29tbXVuaXRpZXMgZ2VudHJpZnkuIAoKVGhlIGNsb3Nlc3QgdG8gdGhlIG5laWdoYm9yaG9vZHMgaW4gb3VyIGRhdGFzZXQgaXMgY2FsbGVkIEFuYWx5c2lzIE5laWdoYm9yaG9vZHMsIGJ1dCB0aGUgbmFtZXMgb2YgdGhlIG5laWdoYm9yaG9vZHMgYWNyb3NzIHRoZXNlIHR3byBkYXRhc2V0cyBzdGlsbCBkb24ndCBlbnRpcmVseSBtYXRjaC4gQmVjYXVzZSBvZiB0aGlzLCB3ZSB3aWxsIG5vdCBqb2luIGJ5IHRoZSBuYW1lIG9mIHRoZSBuZWlnaGJvcmhvb2QsIGJ1dCBpbnN0ZWFkIGJ5IHRoZWlyIHNoYXJlZCBnZW9tZXRyeSAoZXZlcnkgbGF0L2xvbmcgdGhhdCBpcyBpbnNpZGUgdGhlIHBvbHlnb24pLiAKCmBgYHtyfQojQ29udmVydCBhZmZvcmRhYmxlX2hvdXNpbmdfc2YgdG8gYW4gc2Ygb2JqZWN0IHNvIHRoYXQgd2UgY2FuIGpvaW4gaXQgdG8gdGhlIG5laWdoYm9yaG9vZHMgc2hhcGVmaWxlCmFmZm9yZGFibGVfaG91c2luZ19zZl9zZiA8LSBzdF9hc19zZihhZmZvcmRhYmxlX2hvdXNpbmdfc2YsIGNvb3JkcyA9IGMoImxvbmdpdHVkZSIsICJsYXRpdHVkZSIpLCBjcnMgPSA0MzI2KQoKI0pvaW4gbmFtZXMgYW5kIHNoYXBlcyBmcm9tIHNmX3BsYW5uaW5nX25laWdoYm9yaG9vZHMgcG9pbnRzIHRvIHBvaW50cyBmcm9tIGFmZm9yZGFibGVfaG91c2luZ19zZiB3aGVuIHRob3NlIHBvaW50cyBhcmUgaW4gdGhlIHNoYXBlCmFmZm9yZGFibGVfaG91c2luZ19zZl9qb2luZWQgPC0gc3Rfam9pbihhZmZvcmRhYmxlX2hvdXNpbmdfc2Zfc2YsIHNmX3BsYW5uaW5nX25laWdoYm9yaG9vZHMsIGpvaW4gPSBzdF93aXRoaW4pCgojQ2FsY3VsYXRlIHRoZSBudW1iZXIgb2YgYWZmb3JkYWJsZSB1bml0cyBpbiBlYWNoIG5laWdoYm9yaG9vZAphZmZvcmRhYmxlX3VuaXRzX2luX25laWdoYm9yaG9vZCA8LSAKICBhc190aWJibGUoYWZmb3JkYWJsZV9ob3VzaW5nX3NmX2pvaW5lZCkgJT4lIAogIGdyb3VwX2J5KG5ob29kKSAlPiUgCiAgc3VtbWFyaXplKGFmZm9yZGFibGVfdW5pdHMgPSBzdW0oYWZmb3JkYWJsZV91bml0cyksIC5ncm91cHMgPSAnZHJvcCcpICU+JQogIHVuZ3JvdXAoKQoKI0pvaW4gdGhlIGNhbGN1bGF0ZWQgYWZmb3JkYWJsZSB1bml0cyBwZXIgbmVpZ2hib3Job29kIGJhY2sgdG8gdGhlIHNmX3BsYW5uaW5nX25laWdoYm9yaG9vZHMgc2hhcGVmaWxlCmFmZm9yZGFibGVfdW5pdHNfaW5fbmVpZ2hib3Job29kX3NmIDwtIGxlZnRfam9pbihzZl9wbGFubmluZ19uZWlnaGJvcmhvb2RzLCBhZmZvcmRhYmxlX3VuaXRzX2luX25laWdoYm9yaG9vZCkKYGBgCgpOb3cgdXNpbmcgc2ltaWxhciBzdHJhdGVnaWVzIGFzIGFib3ZlIHdlIHdpbGwgY29sb3IgdGhlIG1hcCBieSB0aGUgbnVtYmVyIG9mIGFmZm9yZGFibGUgdW5pdHMgcGxhbm5lZCBmb3IgdGhlIG5laWdoYm9yaG9vZC4gCgpgYGB7cn0KcGFsX2JpbiA8LSBjb2xvckJpbihwYWxldHRlPSJSZWRzIiwgZG9tYWluID0gYWZmb3JkYWJsZV91bml0c19pbl9uZWlnaGJvcmhvb2Rfc2YkYWZmb3JkYWJsZV91bml0cywgYmlucyA9IDEwKQoKbGVhZmxldCgpICU+JQogIHNldFZpZXcobGF0ID0gMzcuNzc0OSwgbG5nID0gLTEyMi40MTk0LCB6b29tID0gMTEpICU+JQogIGFkZFByb3ZpZGVyVGlsZXMoIkNhcnRvREIuUG9zaXRyb24iKSAlPiUKICBhZGRQb2x5Z29ucyhkYXRhID0gYWZmb3JkYWJsZV91bml0c19pbl9uZWlnaGJvcmhvb2Rfc2YsIAogICAgICAgICAgICAgIGxhYmVsID0gfm5ob29kLAogICAgICAgICAgICAgIGZpbGxDb2xvciA9IH5wYWxfYmluKGFmZm9yZGFibGVfdW5pdHMpLCAjZmlsbCBjb2xvciBvZiB0aGUgcG9seWdvbgogICAgICAgICAgICAgIGZpbGxPcGFjaXR5ID0gMC44LCAjZmlsbCBvcGFjaXR5IG9mIHRoZSBwb2x5Z29uCiAgICAgICAgICAgICAgY29sb3IgPSAiIzQ0NDQ0NCIsICNjb2xvciBvZiB0aGUgb3V0bGluZQogICAgICAgICAgICAgIHdlaWdodCA9IDEsICN3ZWlnaHQgb2YgdGhlIG91dGxpbmUKICAgICAgICAgICAgICBzbW9vdGhGYWN0b3IgPSAwLjUgI2hvdyBtdWNoIHRvIHNtb290aCBvdXQgdGhlIGxpbmVzIGluIHRlaCBwb2x5Z29ucwogICAgICAgICAgICAgICkgJT4lCiAgYWRkTGVnZW5kKHRpdGxlID0gIkFmZm9yZGFibGUgVW5pdHMiLCBwYWwgPSBwYWxfYmluLCB2YWx1ZXMgPSBhZmZvcmRhYmxlX3VuaXRzX2luX25laWdoYm9yaG9vZF9zZiRhZmZvcmRhYmxlX3VuaXRzLCBwb3NpdGlvbiA9ICJib3R0b21yaWdodCIpICU+JQogIGFkZENpcmNsZU1hcmtlcnMoZGF0YSA9IGFmZm9yZGFibGVfaG91c2luZ19zZiwKICAgICAgICAgICAgIGxhYmVsID0gfnByb2plY3RfbmFtZSwKICAgICAgICAgICAgIHJhZGl1cyA9IH4yLCAjc2V0cyBzaXplIG9mIGNpcmNsZQogICAgICAgICAgICAgc3Ryb2tlID0gRkFMU0UsICNyZW1vdmVzIHRoZSBvdXRsaW5lCiAgICAgICAgICAgICBjb2xvciA9ICcjNWEzMDliJywgI3NldHMgdGhlIGNvbG9yCiAgICAgICAgICAgICBmaWxsT3BhY2l0eSA9IDAuOCAjc2V0cyB0aGUgb3BhY2l0eQogICAgICAgICAgICAgKQpgYGAK